iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 21
0
Modern Web

Nest.js framework 30天初探系列 第 21

Nestjs framework 30天初探:Day21 Passport(搭配JWT)

  • 分享至 

  • xImage
  •  

Passport

https://ithelp.ithome.com.tw/upload/images/20171224/201071959m1cFwS6V0.png

Passport模組有一個很好的設計模式在裏頭-策略模式,它本身不負責做驗證這工作,我們自個定義驗證方式,給定策略名稱,再經由Passport模組導入驗證策略,執行驗證工作,這讓驗證變得更有彈性好擴充,我們也可以使用第三方驗證不需要自定義驗證邏輯,一樣再透過驗證策略的切換,將第三方驗證作為網站驗證方式。

  1. 沿用Day20專案,安裝本次所需要用到的模組。
    cmd指令
npm install --save passport passport-jwt @types/passport-jwt jsonwebtoken
  1. 在app資料夾底下新增Auth\Passport資料夾。
    cmd指令
cd src/app & mkdir Auth\Passport
  1. Auth資料夾底下新增auth.service.ts。
    src/app/Auth/auth.service.ts
import * as jwt from 'jsonwebtoken';
import { Component, Inject } from '@nestjs/common';
import { UsersServices } from '../Users/users.service';

@Component()
export class AuthService {

    constructor(private readonly usersServices: UsersServices) { }

    public async createToken(id: number) {
        //token到期時間
        const expiresIn = 60*5;
        //重要,盡可能複雜些
        const secret = 'donttalk';
        /*
        payload不建議放淺顯易懂的敏感資料,如要放敏感資料最好有加密過,
        這邊以不重複的id作替代,對應的是資料表ID欄位。
        */
        const token = jwt.sign(id, secret, { expiresIn });
        return {
            expires_in:expiresIn,
            token: token
        }
    }

    public async validate(payload: object): Promise<boolean> {
        //給定where條件,依據token payload的ID作為where條件。
        let queryCondition = { where: { ID: payload['ID'] } };
        const user = await this.usersServices.findOne(queryCondition);
        //有該筆資料,回傳true
        if (user) {
            return true;
        }
        //沒該筆資料回傳false
        else {
            return false;
        }
    }
}
  1. 請在Auth資料夾底下新增,auth.controller.ts
    src/app/Auth/auth.controller.ts
import { Controller, Post, HttpStatus, HttpCode, Get, Body } from '@nestjs/common';
import { AuthService } from './auth.service';

@Controller('auth')
export class AuthController {
    constructor(private readonly authService: AuthService) { }

    @Post('getToken')
    @HttpCode(HttpStatus.OK)
    public async getToken( @Body() ID: number) {
        return await this.authService.createToken(ID);
    }
}

準備一隻可以產生Token的route,等等我們要手動加到HTTP Header測試一下。

  1. 請在Passport資料夾底下新增jwt.strategy.ts。
    src/app/Auth/Passport/jwt.strategy.ts
import * as passport from 'passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { Component, Inject } from '@nestjs/common';
import { AuthService } from '../auth.service';

@Component()
export class JwtStrategy extends Strategy {
    constructor(private readonly authService: AuthService) {
        super({
            //用來帶入驗證的函式
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            //設成true就可以在verify的callback中使用 
            passReqToCallback: true,
            secretOrKey: 'donttalk',
        },
            async (req, payload, next) => await this.verify(req, payload, next)
        );
        passport.use(this);
    }

    public async verify(req, payload, done) {
        //呼叫authService.validate(),會去撈表確認有無資料
        const isValid = await this.authService.validate(payload);
        if (!isValid) {
            return done('驗證失敗', false);
        }
        done(null, payload);
    }
}
  1. 請在Auth底下新增auth.module.ts。
    src/app/Auth/auth.module.ts
import * as passport from 'passport';
import {
    Module,
    NestModule,
    MiddlewaresConsumer,
    RequestMethod
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtStrategy } from './Passport/jwt.strategy';
import { AuthController } from './auth.controller';
import { UsersServices } from '../Users/users.service';
import { UsersProvider } from '../Users/users.providers';
import { DatabaseModule } from '../database.module';
import { UsersController } from '../Users/modules/users.controller';

@Module({
    modules: [DatabaseModule],
    components: [UsersServices,UsersProvider, AuthService, JwtStrategy],
    controllers: [AuthController,UsersController]
})
export class AuthModule implements NestModule {
    //全域middleware
    public configure(consumber: MiddlewaresConsumer) {
        //apply、forRoute方法允許傳入多個參數
        consumber.apply(passport.authenticate('jwt', { session: false }))
            .forRoutes({ path: '/users', method: RequestMethod.ALL });
    }
}
  1. 修改app.module.ts
@Module({
    modules: [UsersModule, AuthModule]
})
  1. 實際測試一下,請打開Postman,對http://localhost:3000/auth/getToken 做POST請求,先拿token一下,模仿登入後,後端產生token給予前端,前端要將token放置安全的地方(不在本次實作範圍)。
    https://ithelp.ithome.com.tw/upload/images/20171224/20107195BPsW9jIxOo.png
  2. 請打開Postman,對http://localhost:3000/auth/getToken 做POST請求,並將token複製到 Header。
    https://ithelp.ithome.com.tw/upload/images/20171224/20107195uYyt9uy4O0.png

大功告成,可以訪問請求到資源,如果沒帶token或token解析驗證錯誤,都會得到Unauthorized

程式碼都在github


上一篇
Nestjs framework 30天初探:Day20 TypeORM(MSSQL)
下一篇
Nestjs framework 30天初探:Day22 MongoDB
系列文
Nest.js framework 30天初探30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言